﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using System.IO;

namespace TheClerk.Forms.Templates.raw.Envelope
{
    #region internal OpaquePayload
    /// <summary>
    /// Payload after symmetric encryption
    /// </summary>
    internal class OpaquePayload
    {
        /// <summary>
        /// Which symmetric algorithm was used to encrypt the Payload
        /// </summary>
        internal Org.BouncyCastle.Crypto.IBlockCipher symmetricAlgorithm;

        /// <summary>
        /// The intended recipients (public keys)
        /// </summary>
        private List<AsymmetricIdentity> recipientList = new List<AsymmetricIdentity>();
        private Chop outermostChop;
        private byte[] symmetricSecret;

        /// <summary>
        /// The non-opaque content of the transmission
        /// </summary>
        internal Payload payload;

        internal OpaquePayload()
        {
            payload = new Payload();
        }

        internal StrongBox strongbox;

        internal void AddRecipient(AsymmetricIdentity recipient)
        {
            recipientList.Add(recipient);
        }

        internal OpaquePayload(StrongBox sbox)
        {
            strongbox = sbox;
            payload = new Payload();
        }

        public Stream Serialize()
        {
            // If recipientList is empty, there is no need to create the
            // OpaquePayload -- the OpaquePayload is essentially just the
            // unencrypted Payload.  If recipientList is empty, strongbox.SymKey
            // is set to null, and OpaquePayload serializes unencrypted.
            //
            // The "null" encryption algorithm 
            // is used when there's no particular desire or capacity for encryption,
            // such as when the Envelope is used to carry multiple certificate
            // chains for a single website.

            // Need to serialize Payload
            byte[] payload = this.payload.ToByteArray();

            // If recipient list is non-empty, generate the symmetric secret
            if (recipientList.Count > 0)
            {
                var bitgen = new Org.BouncyCastle.Crypto.Prng.CryptoApiRandomGenerator();
                var sr = new Org.BouncyCastle.Security.SecureRandom(bitgen);
                KeyGenerationParameters kg = new KeyGenerationParameters(sr,
                    192 / 8);  // AES192
                CipherKeyGenerator ckg = new CipherKeyGenerator();
                ckg.Init(kg);
                symmetricSecret = ckg.GenerateKey();
                strongbox.SetSymKey(symmetricSecret);
            }
            else
            {
                strongbox.SetSymKey(null);
            }

            byte[] opaqueOutput;

            var outStream = new MemoryStream();
            var dsg = new Org.BouncyCastle.Asn1.DerSequenceGenerator(outStream);

            // Now that we have the key, we need to encrypt.  We're using AES-192.
            if (strongbox.SymKey != null)
            {
                var aes192cipher = Org.BouncyCastle.Security.CipherUtilities.GetCipher("AES192");
                aes192cipher.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(strongbox.SymKey));

                opaqueOutput = aes192cipher.DoFinal(payload);
                dsg.AddObject(Org.BouncyCastle.Security.CipherUtilities.GetObjectIdentifier("AES192"));
                dsg.AddObject(new Org.BouncyCastle.Asn1.DerOctetString(opaqueOutput));
            }
            else
            {
                opaqueOutput = payload;
                dsg.AddObject(new Org.BouncyCastle.Asn1.DerObjectIdentifier("2.23.133.4.1")); // that's NULL per oid-info
                dsg.AddObject(new Org.BouncyCastle.Asn1.DerOctetString(opaqueOutput));
            }
            dsg.Close();
            outStream.Seek(0, SeekOrigin.Begin);
            return outStream;
        }

        internal byte[] AsByteArray()
        {
            var s = Serialize();
            var tmp = new byte[s.Length];
            s.Read(tmp, 0, tmp.Length);
            return tmp;
        }

        public Asn1Encodable ToAsn1Encodable()
        {
            return Asn1Object.FromStream(Serialize());
        }

        internal void SetOuterChop(AsymmetricCipherKeyPair keypair)
        {
            SetOuterChop(new Chop(keypair));
        }

        internal void SetOuterChop(Chop chop)
        {
            outermostChop = chop;

            var spki = Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(chop.identityChop.Public);
            var assertion = new Assertion(null, spki.GetDerEncoded(), new AssertionType("application/x-envelope-spki"), null, null);
            payload.AddAssertion(assertion);
            payload.AddChop(chop);
        }

        internal void AddChop(Chop equipment)
        {
            payload.AddChop(equipment);
        }

        internal void AddAssertion(Assertion a)
        {
            payload.AddAssertion(a);
        }
    }
    #endregion
}
